﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MachineLearning;

namespace annulus
{
    class Program
    {
        public static double dist1d(object a, object b)
        {
            double ad = (double)a;
            double bd = (double)b;
            return Math.Abs(ad - bd);
        }

        // The point is 'in' if the radius is > 0.5 and < 1.0
        public static int InAnnulus(double x, double y)
        {
            var radius = Math.Sqrt(x*x + y*y);
            return (radius > 0.5 && radius < 1.0) ? 1 : -1;
        }

        static void Main(string[] args)
        {
            if (args.Length < 1)
            {
                Console.WriteLine("\nUsage: annulus.exe n\n");
                Console.WriteLine("This program tests the performance of rank knn using a simple annulus to train and");
                Console.WriteLine("test data of size n. Random data is generated between -1,-1 and +1,+1. If the distance from");
                Console.WriteLine("the origin to the data point is between 0.5 and 1.0, we are within the annulus (true), ");
                Console.WriteLine("otherwise we are outside(false). We test using data generated in the same way");
                Console.WriteLine("and evaluate the sensitivity and specifity of the method thus.\n");
                return;
            }

            // Create n data points for training 
            int i, n = Int32.Parse(args[0]);

            var data = new List<object[]>();
            object[] x = new object[n];
            object[] y = new object[n];
            int[] labels = new int[n];
            var rng = new Random(DateTime.Now.Millisecond);
            for (i = 0; i < n; i++)
            {
                // Create a random point between -1,-1 and +1,+1
                x[i] = 2.0*rng.NextDouble() - 1.0;
                y[i] = 2.0*rng.NextDouble() - 1.0;

                labels[i] = InAnnulus((double)x[i], (double)y[i]);
            }
            data.Add(x);
            data.Add(y);

            var distanceFunctions = new List<Func<object, object, double>>();
            distanceFunctions.Add(dist1d);
            distanceFunctions.Add(dist1d);

            // Load the training data
            var knn = new RankKNN(data, labels, distanceFunctions, null);

            int positives = 0, truePositives = 0, negatives = 0, trueNegatives = 0;
            // And now test, calculating true negatives and true positives
            for (i = 0; i < n; i++)
            {
                double xd = 2.0 * rng.NextDouble() - 1.0;
                double yd = 2.0 * rng.NextDouble() - 1.0;
                int label = InAnnulus(xd, yd);

                List<object> testData = new List<object>();
                testData.Add(xd);
                testData.Add(yd);

                // Classify the test data point using the nearest 7 neighbours
                int[] knnLabels = knn.Classify(testData, 7);

                // Just take the top label for now - tie breakers won't happen in a binary system
                // with an odd K - we are using 7
                int knnLabel = knnLabels[0];

                if (label < 0)
                {
                    negatives++;
                    if (knnLabel == label)
                        trueNegatives++;
                }
                else
                {
                    positives++;
                    if (knnLabel == label)
                        truePositives++;
                }
            }

            double sensitivity = (double)truePositives / (double)positives;
            double specificity = (double)trueNegatives / (double)negatives;

            Console.WriteLine("Number of positives = {0}, number of true positives = {1}", positives, truePositives);
            Console.WriteLine("Number of negatives = {0}, number of true negatives = {1}", negatives, trueNegatives);
            Console.WriteLine("Sensitivity, specificity of Rank KNN using {0} data points = {1},{2}", n, sensitivity, specificity);
        }
    }
}
